using System;
using System.ComponentModel;
using Agron.Data.Contracts;
using Agron.Services.Proxies;
using System.ServiceModel;
using Agron.Security.ClientLayer;
using Agron.Services.Contracts;
using System.Net.NetworkInformation;
using System.Collections.Generic;


namespace Agron.Services.SessionController
{
    /// <summary>
    /// A <see cref="Component"/> that manages the client-part session related workflows.
    /// </summary>
    public class SessionManager : Component
    {
        private static SessionManager _singleton;
        private static readonly object _lockObject = new object();

        private bool _isInSession;
        private Session _currentSession;
        private InstanceContext _instanceContext;
        private MemberListManager _memberListManager;
        private SessionBrokerServiceProxy _sessionBroker;


        /// <summary>
        /// Initializes a new instance of the <see cref="SessionManager"/> class.
        /// </summary>
        private SessionManager()
        {
            NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(OnNetworkAddressChanged);
            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(OnNetworkAvailabilityChanged);
        }

        /// <summary>
        /// Gets the single instance of the <see cref="SessionManager"/> class.
        /// </summary>
        /// <value>
        /// A <see cref="SessionManager"/> that contains the single instance of the <see cref="SessionManager"/> class.
        /// </value>
        public static SessionManager Instance
        {
            get
            {
                if (null == _singleton)
                {
                    lock (_lockObject)
                    {
                        if (null == _singleton)
                            _singleton = new SessionManager();
                    }
                }

                return _singleton;
            }
        }

        /// <summary>
        /// Occurs when a session is successfully started.
        /// </summary>
        public event EventHandler SessionStarted;

        /// <summary>
        /// Occurs when a session finishes.
        /// </summary>
        public event EventHandler<SessionFinishedEventArgs> SessionFinished;

        /// <summary>
        /// Gets the current session.
        /// </summary>
        /// <value>
        /// A <see cref="SessionManager"/> that contains the current session.
        /// </value>
        public Session CurrentSession { get { lock (_lockObject) { return this._currentSession; } } }

        /// <summary>
        /// Gets a value indicating whether this instance is in session.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is in session; otherwise, <c>false</c>.
        /// </value>
        public bool IsInSession { get { lock (_lockObject) { return this._isInSession; } } }

        /// <summary>
        /// Joins the specified session.
        /// </summary>
        /// <param name="memberListController">An <see cref="IMemberListController"/> implementation that contains the member list controller.</param>
        /// <param name="session">A <see cref="Session"/> that represents that session that has is starting.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">When <paramref name="memberListController"/> or <paramref name="session"/> are <c>null</c>.</exception>
        public JoinSessionResult JoinSession(IMemberListController memberListController, Session session)
        {
            if (null == memberListController)
                throw new ArgumentNullException("memberListController");
            if (null == session)
                throw new ArgumentNullException("session");

            JoinSessionResult returnValue = JoinSessionResult.None;

            try
            {
                lock (_lockObject)
                {
                    this._isInSession = true;
                    this._currentSession = session;
                    this._memberListManager = new MemberListManager(memberListController);
                    this._instanceContext = new InstanceContext(this._memberListManager);
                    this._sessionBroker = new SessionBrokerServiceProxy(this._instanceContext);
                }

                returnValue = this._sessionBroker.JoinSession(
                    /*sessionId*/ session.SessionId,
                    /*userId*/ AgronIdentity.GetCurrent().Profile.UserId,
                    /*internetIPAddress*/ null,
                    /*localIPAddress*/ NetworkHelper.LocalNetworkIP);

                IEnumerable<SessionMember> memberList = this._sessionBroker.GetSessionMembers(session.SessionId);
                memberListController.DataBind(memberList);
            }
            catch
            {
                // Revert state
                this.Dispose(true);

                throw;
            }

            return returnValue;
        }

        /// <summary>
        /// Leaves the current session.
        /// </summary>
        public void LeaveSession()
        {
            this.LeaveSessionInternal(true);
        }

        /// <summary>
        /// Handles the NetworkAvailabilityChanged event of the NetworkChange control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Net.NetworkInformation.NetworkAvailabilityEventArgs"/> instance containing the event data.</param>
        private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            if (this.IsInSession)
            {
                this.LeaveSession();
                this.OnSessionFinished(new SessionFinishedEventArgs(Properties.Resources.SessionManagerAddressChanged));
            }
        }

        /// <summary>
        /// Handles the NetworkAddressChanged event of the NetworkChange control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnNetworkAddressChanged(object sender, EventArgs e)
        {
            if (this.IsInSession)
            {
                this.LeaveSession();
                this.OnSessionFinished(new SessionFinishedEventArgs(Properties.Resources.SessionManagerAddressChanged));
            }
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="T:System.ComponentModel.Component"></see> and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                if (null != this._memberListManager)
                    this._memberListManager.Dispose();

                if (CommunicationState.Opened == this._sessionBroker.State)
                    this._sessionBroker.Close();
            }

            lock (_lockObject)
            {
                this._isInSession = false;
                this._currentSession = null;
                this._memberListManager = null;
                this._instanceContext = null;
                this._sessionBroker = null;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:SessionStarted"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnSessionStarted(EventArgs e)
        {
            EventHandler eventHandler = this.SessionStarted;
            if (null != eventHandler)
                eventHandler(this, e);
        }

        /// <summary>
        /// Raises the <see cref="E:SessionFinished"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SessionFinishedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnSessionFinished(SessionFinishedEventArgs e)
        {
            EventHandler<SessionFinishedEventArgs> eventHandler = this.SessionFinished;
            if (null != eventHandler)
                eventHandler(this, e);
        }

        private void LeaveSessionInternal(bool raiseFinishedEvent)
        {
            if (null != this._sessionBroker)
                this._sessionBroker.LeaveSession(this.CurrentSession.SessionId, AgronIdentity.GetCurrent().Profile.UserId);

            if (raiseFinishedEvent)
                this.OnSessionFinished(new SessionFinishedEventArgs());

            this.Dispose();
        }
    }
}
